package client;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.util.concurrent.Semaphore;

import javax.swing.JComponent;
import javax.swing.Timer;

import utils.GameImages;
import utils.Piece;
import enums.Weapon;

/**
 * The animation of a piece moving from one cell to another.
 */
public class MovePieceAnimation extends JComponent {

	private static final long serialVersionUID = 1L;
	
	private static final double ANIMATION_TIME = 0.7; // 1.3;
	private static final double GROWTH = 0.02;
	private static final int    MOVE_PIECE_ANIMATION_SPEED = 50;
	private static final double NUM_OF_HALF_ANIMATION_MOVES = ((ANIMATION_TIME) / (MOVE_PIECE_ANIMATION_SPEED / 1000.0)) / 2;
	private static final double SCALE_FACTOR_MIN_VALUE = 1;
	private static final double SCALE_FACTOR_MAX_VALUE = 13;
	private static final int    COUNTER_MIN_VALUE = 1;
	
	private int counter = 1;
	private Timer timerMovePiece = null;
	private Board board;
	public  Point from;
	public  Point to;
	private TableFrame tableFrame;
	private Semaphore sem;
	private boolean down = false;
	private Piece fromPiece = null;
	private Piece toPiece = null;

	
	public MovePieceAnimation() {
		super();
	}
	
	public void stop() {  
		timerMovePiece.stop();
	}
	
	public void setValues(Board board, Point from, Point to, TableFrame tableFrame) {
		this.board = board;
		this.from = from;
		this.to = to;
		this.tableFrame = tableFrame;
		
		this.fromPiece = (from.equals(Board.createOutOfBoardPoint()) == false) ? new Piece(board.model.getPiece(from).getMap()) : null;
		this.toPiece = (to.equals(Board.createOutOfBoardPoint()) == false) ? board.model.getPiece(to) : null;
	}
	
	public void draw(Graphics g) {

		Graphics2D g2d = (Graphics2D) g;
		AffineTransform af = new AffineTransform();

		double currentX = g2d.getTransform().getTranslateX();
		double currentY = g2d.getTransform().getTranslateY();

		if (toPiece.weapon == Weapon.TRAP) {
			Point sCoordsTo = board.getScreenCoords ((int)to.getX(),(int) to.getY());
			int transXFigure = 20;
			int transYFigure = -20;
			int rectX = 0;
			int rectY = 50;
			AffineTransform afTo = new AffineTransform();
			if ((toPiece.color == board.model.playerColor)) {
				transXFigure = -transXFigure + 10;
				transYFigure = -transYFigure - 32;

				rectX += 20;
				rectY -= 30;
			}
			afTo.translate(currentX + sCoordsTo.getX() + transXFigure, currentY + sCoordsTo.getY() + transYFigure);

			g2d.setTransform(afTo);
			g2d.setColor(Color.BLACK);
			g2d.fillRect(rectX, rectY, 30, 30);
			double shearX = 0.3;
			double shearY = 0.7;

			afTo.shear(shearX, shearY);
			g2d.setTransform(afTo);
			String mapTo = toPiece.getMap();
			if ((toPiece.color != board.model.playerColor)) {
				mapTo = Piece.changeProperty(mapTo, Piece.WeaponPosition, Weapon.NONE.ordinal());
			}
			g2d.drawImage(GameImages.getImage(mapTo), 0, 0, null);

		}
		Point sCoords = board.getScreenCoords ((int)from.getX(),(int) from.getY());
		String map = board.getMapForDrawing(fromPiece);
		
		if (GameImages.getImageIcon(map) == null)
		{
			return;
		}
		
		double fromWidth = GameImages.getImageIcon(map).getIconWidth();
		double fromHeight = GameImages.getImageIcon(map).getIconHeight();

		g2d.setTransform(af);
		double scale = calcScale();
		af.scale(scale, scale);
		calcTranslate(af, sCoords , currentX , currentY , fromWidth , fromHeight);
		g2d.setTransform(af);

		g2d.drawImage(GameImages.getImage(map), 0, 0, null);
	}

	private void calcTranslate(AffineTransform af , Point sCoords , double currentX, double currentY ,double fromWidth , double fromHeight ) {
		double scaling = calcScale();
		int    totalCellSize = board.getTotalCellSize();
		
		Point pos = new Point ((int)(to.getX() - from.getX()) ,(int)(to.getY() - from.getY()));
		
		double xIndent = (Board.CELL_SIZE - fromWidth  * ( (pos.x !=0) ? 1 : scaling ) ) / 2 ;
		double yIndent = (Board.CELL_SIZE - fromHeight * ( (pos.y !=0) ? 1 : scaling ) ) / 2 ;
		
		double moveAxis = (down) ? (2*NUM_OF_HALF_ANIMATION_MOVES - counter) : counter;
		double totalMoveAxis = (totalCellSize / (2*NUM_OF_HALF_ANIMATION_MOVES) ) * moveAxis;
		
		int reverseBool = board.isReversed() ? -1 : 1;
		
		double xTranslate = currentX + sCoords.x + xIndent + (totalMoveAxis * pos.x * reverseBool);
		double yTranslate = currentY + sCoords.y + yIndent + (totalMoveAxis * pos.y * reverseBool);
		
		af.translate(xTranslate / scaling, yTranslate / scaling);
	}

	private void animate() {
		timerMovePiece = new Timer(MOVE_PIECE_ANIMATION_SPEED, new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				// NUM_OF_HALF_ANIMATION_MOVES can be a non-integer, depending on the values it calculates, so we must check >=
				if (counter >= NUM_OF_HALF_ANIMATION_MOVES  || ((down) && (counter > 0)) ) {
					down = true;
					counter--;
				}
				else if (counter > 0) {
					counter++; 
				}
				else {
					timerMovePiece.stop();
					sem.release();
					tableFrame.getModel().trapHide = false;
					down = false;
					counter = COUNTER_MIN_VALUE;
				}
				tableFrame.repaint(); 
			}
		});
		timerMovePiece.start();	 
	}
	public void animate(Semaphore s) {
		sem = s;
		animate();
	}
	
	/**
	 * When moving a piece, it grows and then shrinks to its original size while moving.
	 * This function calculates the scaling in every frame of the animation.
	 * 
	 * @return The scale of the piece for the current frame.
	 */
	private double calcScale()
	{
		double factoredCounter = COUNTER_MIN_VALUE + (counter - COUNTER_MIN_VALUE) * (SCALE_FACTOR_MAX_VALUE - SCALE_FACTOR_MIN_VALUE) / (NUM_OF_HALF_ANIMATION_MOVES - COUNTER_MIN_VALUE);
		
		double factor = SCALE_FACTOR_MAX_VALUE - (SCALE_FACTOR_MAX_VALUE - SCALE_FACTOR_MIN_VALUE) * Math.pow((1 - (factoredCounter - SCALE_FACTOR_MIN_VALUE) / (SCALE_FACTOR_MAX_VALUE - SCALE_FACTOR_MIN_VALUE)), 3);

		return 1 + factor*GROWTH;
	}
}